/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.pz.net;

import com.pz.util.MessageMannager;
import com.pz.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.BindException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.LinkedList;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author jannek
 */
public class Server {

    public final ServerInfo info;
    private Thread thread = null;
    private boolean running = false;
    private NetworkListener listener;
    private boolean connected;
    private final MessageMannager m;
    private final LinkedList<Other> clients;
    private final LinkedList<Other> clientsRemove;
    private final LinkedList<Other> clientsNew;
    private final LinkedList<Other> clientsNewRemove;
    private final LinkedList<Other> clientsDisconnecting;
    private final LinkedList<Other> clientsDisconnectingRemove;
    private final TreeMap<Integer, Other> clientsArranged;
    public final DatagramChannel udp;
    private final DatagramSocket socket;
    private ByteBuffer smallBuffer;
    private ByteBuffer bigBuffer;
    private ByteBuffer confirm;
    private SocketAddress from;
    private int fromId;
    private long loopCount = 0;

    public Server(ServerInfo info) throws IOException {
        this.info = info;

        m = MessageMannager.getManager();

        clients = new LinkedList<Other>();
        clientsRemove = new LinkedList<Other>();
        clientsNew = new LinkedList<Other>();
        clientsNewRemove = new LinkedList<Other>();
        clientsDisconnecting = new LinkedList<Other>();
        clientsDisconnectingRemove = new LinkedList<Other>();
        clientsArranged = new TreeMap<Integer, Other>();

        smallBuffer = ByteBuffer.allocate(MessageMannager.MAX_LENGHT);
        bigBuffer = ByteBuffer.allocate(MessageMannager.MAX_LENGHT_TOTAL);

        udp = DatagramChannel.open();
        udp.configureBlocking(false);
        socket = udp.socket();
        socket.bind(null);
        Log.d("Server is bound to port: " + socket.getLocalPort());
    }

    public void connect(NetworkListener listener, MasterServerLink link) {
        this.listener = listener;
        link.serverUdp(this);
    }

    public void runAsThread() {
        running = true;
        thread = new Thread() {

            @Override
            public void run() {
                while (running) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException ex) {
                        Log.d(ex);
                    }
                    try {
                        update();
                    } catch (IOException ex) {
                        Log.d(ex);
                    }
                }
            }
        };
        thread.start();
    }

    public void stopThread() {
        running = false;
    }

    public synchronized void update() throws IOException {
        try {
            if (nextMasterCheck < System.nanoTime()) {
                BufferedReader br = m.url("update&id=" + id
                        + "&key=" + key);
                if (br != null) {
                    int clientId = Integer.parseInt(br.readLine());
                    String name = br.readLine();
                    String[] clientAddress = br.readLine().split(":");
                    int clientPort = Integer.parseInt(clientAddress[1]);
                    Other other = new Other(clientId, name,
                            new InetSocketAddress(clientAddress[0],
                            clientPort), id, listener);
                    clientsNew.add(other);
                    clientsArranged.put(clientId, other);

                    Log.d("Client " + name + " id " + clientId + " added.");

//					smallBuffer.putInt(id);
//					smallBuffer.put(Message.HELLO);
//
//					other.send(bigBuffer);

                } else {
                    nextMasterCheck = System.nanoTime() + masterCheckDelay;
                }
            }

            while ((from = udp.receive(bigBuffer)) != null) {
//				Log.d(from.toString());
                bigBuffer.flip();
//				Log.d("Received from " + bigBuffer.getInt(0) + " @" +
//						from.toString() + " " + bigBuffer.remaining() +
//						" bytes!");
                fromId = bigBuffer.getInt();
                if (clientsArranged.containsKey(fromId)) {
                    if (null != (confirm = clientsArranged.get(fromId).
                            input(bigBuffer, from))) {
                        udp.send(confirm, from);
                    }
                }

                bigBuffer.clear();
            }

        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (!clientsNew.isEmpty()) {
            for (Other o : clientsNew) {
                if (o.isConnected()) {
                    clients.add(o);
                    clientsNewRemove.add(o);
                } else {
                    o.flush(udp);
                }
            }

            for (Other o : clientsNewRemove) {
                clientsNew.remove(o);
            }
            clientsNewRemove.clear();
        }

        synchronized (clients) {
            for (Other o : clients) {
                o.flush(udp);
                if (o.isDisconnected()) {
                    clientsRemove.add(o);
                }
            }
            for (Other o : clientsRemove) {
                clients.remove(o);
                clientsArranged.remove(o.getId());
            }
        }

        if (!clientsDisconnecting.isEmpty()) {
            for (Other o : clientsDisconnecting) {
                o.flush(udp);
                if (o.isDisconnected()) {
                    clientsDisconnectingRemove.add(o);
                }
            }

            for (Other o : clientsDisconnectingRemove) {
                clientsDisconnecting.remove();
                clientsArranged.remove(o.getId());
//				listener.onDisconnect(o, o.disconnectReason());
            }
            clientsDisconnectingRemove.clear();
        }
    }

    public void disconnect(Other client, byte reason) {
        client.disconnect(reason);
        clientsDisconnecting.add(client);
        clients.remove(client);
    }

    public void close(int secTimeOut) {
        Log.d("Close server!");
        connected = false;
        stopThread();
        double time = System.nanoTime() + secTimeOut * 1e9;
        synchronized (clients) {
            Other o;
            while ((o = clients.poll()) != null) {
                disconnect(o, MessageMannager.DISCONNECT_CLOSE);
            }
        }

        while (time > System.nanoTime()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                Log.d(ex);
            }
            try {
                update();
            } catch (IOException ex) {
                Log.d(ex);
            }
            if (clientsDisconnecting.isEmpty()) {
                break;
            }
        }
//		for (Other o : clientsDisconnecting)
//			listener.onDisconnect(o, o.disconnectReason());

        try {
            synchronized (udp) {
                udp.close();
            }
        } catch (IOException ex) {
            Log.d(ex);
        }

        Log.d("Server is closed. With " + clientsDisconnecting.size()
                + " unresolved clients.");
    }
}
